package nf;

import java.io.File;
import java.util.*;
import nf.graph.*;
import nf.util.*;
import nf.utility.UtilityCalculator;
import nf.utility.UtilityTracer;

public class ValueAnalyzer
{
	private UtilityCalculator utilCalc;
	
	public ValueAnalyzer(UtilityCalculator utilCalc)
	{
		this.utilCalc = utilCalc;
	}
	
	public Map<Node, Breakdown> analyze(Graph graph)
	{
		class Tracer implements UtilityTracer
		{
			private Map<Node, Breakdown> breakdowns;
			
			Tracer(Collection<Node> nodes)
			{
				breakdowns = new HashMap<Node, Breakdown>();
				for (Node n : nodes)
					breakdowns.put(n, new Breakdown(n));
			}
			
			public Map<Node, Breakdown> getBreakdowns()
			{
				return breakdowns;
			}
			
			public void traceValue(Node node, Rational value, Node pathHead,
				Node pathTail)
			{
				Breakdown b = breakdowns.get(node);
				b.addValue(pathHead, pathTail, value);
			}
		}
		
		Tracer tracer = new Tracer(graph);
		utilCalc.addTracer(tracer);
		try {
			utilCalc.getUtilities(graph); // all real work done in the tracer
			return tracer.getBreakdowns();
		} finally {
			utilCalc.removeTracer(tracer);
		}
	}
	
	private static void analyze(UtilityCalculator uc, Iterator<Graph> graphs)
	{
		ValueAnalyzer analyzer = new ValueAnalyzer(uc);
		class NodeComparator implements Comparator<Node>
		{
			private Map<Node, Breakdown> breakdowns;
			
			public NodeComparator(Map<Node, Breakdown> breakdowns)
			{
				this.breakdowns = breakdowns;
			}
			
			public boolean equals(Object o)
			{
				return this == o;
			}
			
			public int compare(Node a, Node b)
			{
				Breakdown ba = breakdowns.get(a);
				Breakdown bb = breakdowns.get(b);
				return ba.getTotalValue().compareTo(bb.getTotalValue());
			}
		}
		
		while (graphs.hasNext()) {
			Graph g = graphs.next();
			Map<Node, Breakdown> bds = analyzer.analyze(g);
			List<Node> nodes = new ArrayList<Node>(g);
			Collections.sort(nodes, new NodeComparator(bds));
			
			for (Node n : nodes) {
				System.out.print(n.getName() + ':');
				for (Node neighbor : n.getNeighbors()) {
					System.out.printf(" %s", neighbor.getName());
				}
				System.out.println();
				
				Breakdown bd = bds.get(n);
				Rational all = bd.getTotalValue();
				Rational end = bd.getEndpointValue();
				Rational mid = bd.getMiddlemanValue();
				System.out.printf("    => all=%s (%.3f), end=%s (%.3f), " +
					"mid=%s (%.3f)%n",
					all, all.doubleValue(),
					end, end.doubleValue(),
					mid, mid.doubleValue());
			}
			
			System.out.println("===============================");
		}
	}
	
	private static Iterator<Graph> randomStableGraphs(final EdgeGame game,
		int nodeCount)
	{
		GraphGenerator gen = new GraphGenerator(nodeCount);
		
		class StableGraphIterator extends FilteringIterator<Graph>
		{
			public StableGraphIterator(Iterator<Graph> inner)
			{
				super(inner);
			}
			
			protected boolean accept(Graph graph)
			{
				List<Rational> bounds = game.getCostBounds(graph);
				return bounds.get(0).compareTo(bounds.get(1)) <= 0;
			}
		}
		
		return new DistinctGraphIterator(
			new StableGraphIterator(gen.iterator()),
			nodeCount
		);
	}
	
	public static void main(String... args)
	{
		ArgumentParser p = new ArgumentParser();
		
		p.addOption("file", "-f", "--file").setValueType(File.class);
		p.addOption("nodeCount", "-n", "--node-count").setValueType(Integer.class);
		p.addOption("utility", "-u", "--util-calc").setValueType(String.class)
			.setDefaultValue("default");
			
		try {
			ArgumentParser.Result result = p.parse(args);
			
			File graphFile = (File) result.options.get("file");
			Integer nodeCount = (Integer) result.options.get("nodeCount");
			Iterator<Graph> graphIter = null;
			UtilityCalculator uc = Simulator.getUtilityCalculator(
				(String) result.options.get("utility"));
			
			if (graphFile == null && nodeCount == null) {
				throw new ArgumentParseException("Must provide either " +
					"a graph file or a node count.");
			} else if (graphFile != null && nodeCount != null) {
				throw new ArgumentParseException("Cannot provide both " +
					"a graph file and a node count.");
			} else if (graphFile != null) {
				List<Graph> graphs =
					Collections.singletonList(Simulator.readNetwork(graphFile));
				graphIter = graphs.iterator();
			} else if (nodeCount != null) {
				graphIter = randomStableGraphs(new FormationGame(null, uc),
					nodeCount);
			}
			
			analyze(uc, graphIter);
		} catch (ArgumentParseException e) {
			System.err.println("error: " + e.getMessage());
			System.exit(-1);
			return;
		}
	}
	
	public static class Breakdown
	{
		private Node node;
		private Rational asEndpoint;
		private Rational asMiddleman;
		private Rational totalValue;
		private Map<Path, Rational> values;
		
		Breakdown(Node node)
		{
			this.node = node;
			this.values = new HashMap<Path, Rational>();
			
			asEndpoint = new Rational();
			asMiddleman = new Rational();
			totalValue = null;
		}
		
		public void addValue(Node head, Node tail, Rational value)
		{
			Path p = new Path(head, tail, new ArrayList<Node>(0));
			values.put(p, value);
			
			if (head.equals(node) || tail.equals(node)) {
				asEndpoint = asEndpoint.add(value);
			} else {
				asMiddleman = asMiddleman.add(value);
			}
			totalValue = null;
		}
		
		/**
		 * Returns the node to which this breakdown applies.
		 * @return node to which this breakdown applies
		 */
		public Node getNode() {
		    return node;
		}
		
		/**
		 * Returns the total value of the node.
		 * @return total value of the node
		 */
		public Rational getTotalValue() {
			if (totalValue == null)
				totalValue = asEndpoint.add(asMiddleman);
		    return totalValue;
		}
		
		/**
		 * Returns the value that the node receives as an endpoint on paths.
		 * @return value that the node receives as an endpoint on paths
		 */
		public Rational getEndpointValue() {
		    return asEndpoint;
		}
		
		/**
		 * Returns the value that the node receives as a middleman on paths.
		 * @return value that the node receives as a middleman on paths
		 */
		public Rational getMiddlemanValue() {
		    return asMiddleman;
		}
		
		/**
		 * Returns the full value breakdown. Only the head and tail properties
		 * on the paths are valid.
		 * @return full value breakdown
		 */
		public Map<Path, Rational> getValues() {
		    return values;
		}
	}
}
